package com.jkgh.remedium.rsgen.generator.flash;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jkgh.remedium.rsgen.vo.Argument;
import com.jkgh.remedium.rsgen.vo.MethodInfo;
import com.jkgh.remedium.rsgen.vo.MethodObject;

public class RemediumMethodObjectFlashGenerator {

	private static final Map<String, String> CLASS_BY_TYPE = new HashMap<String, String>();

	private static final Map<String, String> RESPONDER_BY_TYPE = new HashMap<String, String>();

	static {
		CLASS_BY_TYPE.put("double", "Number");
		CLASS_BY_TYPE.put("byte", "int");
		CLASS_BY_TYPE.put("double[]", "Vector.<Number>");
		CLASS_BY_TYPE.put("byte[]", "ByteArray");

		RESPONDER_BY_TYPE.put("int", "Int");
		RESPONDER_BY_TYPE.put("int[]", "IntArray");
		RESPONDER_BY_TYPE.put("byte", "Byte");
		RESPONDER_BY_TYPE.put("byte[]", "ByteArray");
		RESPONDER_BY_TYPE.put("double", "Double");
		RESPONDER_BY_TYPE.put("double[]", "DoubleArray");
		RESPONDER_BY_TYPE.put("String", "String");
		RESPONDER_BY_TYPE.put("String[]", "StringArray");
	}

	public static void generate(File outputDir, MethodObject m, String packageName) throws FileNotFoundException {

		File outFile = new File(outputDir, m.getClassName() + ".as");
		PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(outFile)));

		out.println("package " + packageName + " {");
		out.println();
		out.println("	import com.jkgh.remedium.*;");
		out.println("	import com.jkgh.remedium.objects.*;");
		out.println("	import com.jkgh.remedium.objects.messages.*;");
		out.println("	import com.jkgh.remedium.objects.messages.ins.*;");
		out.println("	import com.jkgh.remedium.objects.messages.out.*;");
		out.println("	import com.jkgh.remedium.states.*;");
		out.println("	import com.jkgh.remedium.tools.*;");
		out.println();
		out.println("	/**");
		out.println("	 * " + m.getDescription());
		out.println("	 * ");
		out.println("	 * @author RS2FGenerator");
		out.println("	 */");
		out.println("	public class " + m.getClassName() + " extends RemediumMethodObject {");
		out.println();

		for (MethodInfo mt : m.getMethods()) {
			out.println("		/**");
			out.println("		 * " + mt.getDescription());
			out.println("		 * ");

			for (Argument a : mt.getArguments()) {
				out.println("		 * @param " + a.getName() + " " + a.getDescription());
			}

			if (mt.getReturnType() != null) {
				out.println("		 * ");
				out.println("		 * @param returner Should return " + mt.getReturnType().getClassName() + ": " + mt.getReturnType().getDescription() + ". ");
			}

			String as = "";
			for (Argument a : mt.getArguments()) {
				as += a.getName() + ":" + a.getClassName() + ", ";
			}

			if (mt.getReturnType() != null) {
				as += "Returner<" + mt.getReturnType().getClassName() + "> returner";
			} else if (as.length() > 0) {
				as = as.substring(0, as.length() - 2);
			}

			out.println("		 */");
			out.println("		public function " + mt.getName() + "(" + as + "):void {");
			out.println("			throw new AbstractMethodNotImplementedError();");
			out.println("		}");
			out.println();
		}

		out.println("		// --------------------------------------------------------------------------------");
		out.println();
		out.println("		public function " + m.getClassName() + "(remedium:RemediumSystem, hostID:int = -1, objectID:int = -1, registry:RemediumSystem = null) {");
		out.println("			super(remedium, hostID, objectID, registry);");
		out.println("		}");
		out.println();
		out.println("		override public final function call(remedium:RemediumSystem, message:RemediumInMessage, callID:int, methodID:int, callback:Function):void {");
		out.println("			switch (methodID) {");
		for (int i = 0; i < m.getMethods().size(); ++i) {
			MethodInfo mt = m.getMethods().get(i);

			out.println();
			out.println("				case " + i + ":");

			String args = "";
			for (Argument a : mt.getArguments()) {
				args += a.getName() + i + ", ";
			}

			List<String> ending = new ArrayList<String>();
			if (mt.getReturnType() == null) {
				if (args.length() > 0) {
					args = args.substring(0, args.length() - 2);
				}
				ending.add(mt.getName() + "(" + args + ");");
				ending.add("callback();");
			} else {
				ending.add(mt.getName() + "(" + args + "function(r:" + classForType(mt.getReturnType().getClassName()) + "):void {");
				ending.add("");
				ending.add("	var responder:RemediumC2S" + responderForType(mt.getReturnType().getClassName()) + "Responder = new RemediumS2C" + responderForType(mt.getReturnType().getClassName())
					+ "Responder(callID, r);");
				ending.add("	responder.produce(remedium);");
				ending.add("});");
				ending.add("callback();");
			}

			RemediumArgumentConsumerFlashGenerator.generate(out, 5, i, mt.getArguments(), ending);

			out.println("					break;");

		}
		out.println("			}");
		out.println("		}");
		out.println();
		out.println("		public static function createConsumingState(message:RemediumInMessage, future:Function):RemediumState {");
		out.println("			return new GatheringState(1, function(remedium:RemediumSystem):void {");
		out.println();
		out.println("				switch (remedium.produceByte()) {");
		out.println();
		out.println("					case RemediumConstants.NULL:");
		out.println("						future(null);");
		out.println("						break;");
		out.println();
		out.println("					case RemediumConstants.HISTORY:");
		out.println("						future(" + m.getClassName() + "(message.fromHistory(remedium.produceInt())));");
		out.println("						break;");
		out.println();
		out.println("					case RemediumConstants.VALUE:");
		out.println("						consumeContent(remedium, message, future);");
		out.println("						break;");
		out.println("				}");
		out.println("			});");
		out.println("		}");
		out.println();
		out.println("		public static function createVectorConsumingState(message:RemediumInMessage, future:Function):RemediumState {");
		out.println("			return new GatheringState(4, function(remedium:RemediumSystem):void {");
		out.println();
		out.println("				var length:int = remedium.produceInt();");
		out.println("				if (length == -1) {");
		out.println("					future(null);");
		out.println("				} else if (length == 0) {");
		out.println("					future(new Vector.<" + m.getClassName() + ">());");
		out.println("				} else {");
		out.println("					var i:int = 0;");
		out.println("					var result:Vector.<" + m.getClassName() + "> = new Vector.<" + m.getClassName() + ">(length, true);");
		out.println("					var onO:Function = function(o:" + m.getClassName() + "):void {");
		out.println("						result[i++] = o;");
		out.println("						if (i == length) {");
		out.println("							future(result);");
		out.println("						} else {");
		out.println("							remedium.setState(createConsumingState(message, onO));");
		out.println("						}");
		out.println("					};");
		out.println("					remedium.setState(createConsumingState(message, onO));");
		out.println("				}");
		out.println("			});");
		out.println("		}");
		out.println();
		out.println("		private static function consumeContent(remedium:RemediumSystem, message:RemediumInMessage, future:Function):void {");
		out.println("			remedium.setState(new GatheringState(8, function(remedium:RemediumSystem):void {");
		out.println();
		out.println("				var hostID:int = remedium.produceInt();");
		out.println("				var objectID:int = remedium.produceInt();");
		out.println();
		out.println("				var methodObject:" + m.getClassName() + " = " + m.getClassName() + "(remedium.getMethodObject(hostID, objectID));");
		out.println("				if (methodObject == null) {");
		out.println("					methodObject = new " + m.getClassName() + "Redirector(hostID, objectID, remedium);");
		out.println("				}");
		out.println();
		out.println("				message.toHistory(methodObject);");
		out.println();
		out.println("				future(methodObject);");
		out.println("			});");
		out.println("		}");
		out.println("	}");
		out.println("}");
		out.println();
		out.println("import com.jkgh.remedium.RemediumSystem;");
		out.println("import com.jkgh.remedium.objects.messages.out.RemediumC2SCaller;");
		out.println("import com.jkgh.remedium.tools.RemediumTools;");
		out.println("import " + packageName + "." + m.getClassName() + ";");
		out.println();
		out.println("class " + m.getClassName() + "Redirector extends " + m.getClassName() + " {");
		out.println();
		out.println("	private var remedium:RemediumSystem;");
		out.println();
		out.println("	public function " + m.getClassName() + "Redirector(hostID:int, objectID:int, remedium:RemediumSystem) {");
		out.println("		super(null, hostID, objectID, remedium);");
		out.println("		this.host = server.getHost(hostID);");
		out.println("	}");

		for (int i = 0; i < m.getMethods().size(); ++i) {

			MethodInfo mt = m.getMethods().get(i);

			String finalArgs = "";
			for (Argument a : mt.getArguments()) {
				finalArgs += a.getName() + ":" + a.getClassName() + ", ";
			}

			if (mt.getReturnType() != null) {
				finalArgs += "onReturn:Function";
			} else if (finalArgs.length() > 0) {
				finalArgs = finalArgs.substring(0, finalArgs.length() - 2);
			}

			out.println();
			out.println("	override public function " + mt.getName() + "(" + finalArgs + "):void {");
			out.println("		var caller:RemediumC2SCaller = new RemediumC2SCaller(hostID, objectID, " + i + ", " + (mt.getReturnType() == null ? "0" : "remedium.registerCall(onReturn)")
				+ ", function():void {");

			for (Argument a : mt.getArguments()) {
				out.println("			" + RemediumProducerFlashGenerator.generate(a.getClassName(), a.getName(), "caller") + ";");
			}

			out.println("		});");
			out.println("		caller.produce(host.getMachine());");
			out.println("	}");
		}

		out.println("}");

		out.close();
	}

	private static String responderForType(String type) {

		String ret = RESPONDER_BY_TYPE.get(type);
		if (ret == null) {
			if (type.endsWith("[]")) {
				ret = "ObjectArray";
			} else {
				ret = "Object";
			}
		}
		return ret;
	}

	private static String classForType(String type) {

		String cls = CLASS_BY_TYPE.get(type);
		if (cls == null) {
			cls = type;
		}
		return cls;
	}
}
